
import { Renderer, Camera, Transform, Texture, Program, Geometry, Mesh } from '../../';
import { Orbit, Plane, Shadow } from '../../';

const vertexColor = /* glsl */ `
            attribute vec3 position;
            attribute vec2 uv;

            uniform mat4 modelMatrix;
            uniform mat4 modelViewMatrix;
            uniform mat4 projectionMatrix;

            uniform mat4 shadowViewMatrix;
            uniform mat4 shadowProjectionMatrix;

            varying vec2 vUv;
            varying vec4 vLightNDC;

            // Matrix to shift range from -1->1 to 0->1
            const mat4 depthScaleMatrix = mat4(
                0.5, 0, 0, 0, 
                0, 0.5, 0, 0, 
                0, 0, 0.5, 0, 
                0.5, 0.5, 0.5, 1
            );

            void main() {
                vUv = uv;
                
                // Calculate the NDC (normalized device coords) for the light to compare against shadowmap
                vLightNDC = depthScaleMatrix * shadowProjectionMatrix * shadowViewMatrix * modelMatrix * vec4(position, 1.0);
                gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
            }
        `;

const fragmentColor = /* glsl */ `
            precision highp float;

            uniform sampler2D tMap;
            uniform sampler2D tShadow;

            varying vec2 vUv;
            varying vec4 vLightNDC;

            float unpackRGBA (vec4 v) {
                return dot(v, 1.0 / vec4(1.0, 255.0, 65025.0, 16581375.0));
            }

            void main() {
                vec3 tex = texture2D(tMap, vUv).rgb;

                vec3 lightPos = vLightNDC.xyz / vLightNDC.w;

                float bias = 0.001;
                float depth = lightPos.z - bias;
                float occluder = unpackRGBA(texture2D(tShadow, lightPos.xy));

                // Compare actual depth from light to the occluded depth rendered in the depth map
                // If the occluded depth is smaller, we must be in shadow
                float shadow = mix(0.2, 1.0, step(depth, occluder));

                gl_FragColor.rgb = tex * shadow;
                gl_FragColor.a = 1.0;
            }
        `;


const renderer = new Renderer({ dpr: 2 });
const gl = renderer.gl;
document.body.appendChild(gl.canvas);
gl.clearColor(1, 1, 1, 1);

const camera = new Camera(gl, { fov: 35 });
camera.position.set(5, 4, 10);

const controls = new Orbit(camera);

function resize() {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.perspective({ aspect: gl.canvas.width / gl.canvas.height });
}
window.addEventListener('resize', resize, false);
resize();

const scene = new Transform();

// Swap between the 'fov' and 'left/right/etc' lines to switch from an orthographic to perspective camera,
// and hence, directional light to spotlight projection.
const light = new Camera(gl, {
    left: -3, right: 3, bottom: -3, top: 3,
    // fov: 30,

    near: 1,
    far: 20,
});
light.position.set(3, 10, 3);
light.lookAt([0, 0, 0]);

// Create shadow instance attached to light camera
const shadow = new Shadow(gl, { light });

addAirplane();
addGround();

let airplane;
async function addAirplane() {
    const texture = new Texture(gl);
    const img = new Image();
    img.onload = () => texture.image = img;
    img.src = 'assets/airplane.jpg';

    const program = new Program(gl, {
        vertex: vertexColor,
        fragment: fragmentColor,
        uniforms: {
            tMap: { value: texture },
        },
        cullFace: null,
    });

    const data = await (await fetch(`assets/airplane.json`)).json();

    const geometry = new Geometry(gl, {
        position: { size: 3, data: new Float32Array(data.position) },
        uv: { size: 2, data: new Float32Array(data.uv) },
        normal: { size: 3, data: new Float32Array(data.normal) },
    });

    const mesh = new Mesh(gl, { geometry, program });
    mesh.setParent(scene);

    // Use the 'add' method to attach the mesh to the shadow map
    shadow.add({ mesh });

    airplane = mesh;
}

function addGround() {
    const texture = new Texture(gl);
    const img = new Image();
    img.onload = () => texture.image = img;
    img.src = 'assets/water.jpg';

    const program = new Program(gl, {
        vertex: vertexColor,
        fragment: fragmentColor,
        uniforms: {
            tMap: { value: texture },
        },
        cullFace: null,
    });

    const geometry = new Plane(gl);

    const mesh = new Mesh(gl, { geometry, program });
    mesh.setParent(scene);

    // Use the 'add' method to attach the mesh to the shadow map
    shadow.add({ mesh });

    mesh.rotation.x = Math.PI / 2;
    mesh.scale.set(6);
    mesh.position.y = -3;
}

requestAnimationFrame(update);
function update(t) {
    requestAnimationFrame(update);
    controls.update();

    // A bit of plane animation
    if (airplane) {
        airplane.position.z = Math.sin(t * 0.001);
        airplane.rotation.x = Math.sin(t * 0.001 + 2) * 0.1;
        airplane.rotation.y = Math.sin(t * 0.001 - 4) * -0.1;
    }

    // Render cast meshes to shadow map
    shadow.render({ scene });

    // Render the scene (with shadows) to the canvas
    renderer.render({ scene, camera });
}

document.getElementsByClassName('Info')[0].innerHTML = 'Shadow maps. Model by Google Poly';
document.title = 'OGL • Shadow maps';
